from __future__ import absolute_import

import logging
import traceback
import re
from collections import namedtuple
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from django.utils import timezone
from django.db.models import F
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from obs import RestoreTier, SetObjectMetadataHeader
from monitor.models import MonitorRealTimeInfo, MonitorAlarm
from data_mgt.models import AnnotateData, DataDownloadInfo, ObsAsyncJobStatus
from data_mgt.serializer import AnnotateDataSerializer
from common.tools import record_ip, auth_request, fill_filter_data, set_header
from common.obs_tools import obs_client, object_restore_status, get_job_type
from common.ploto_enum import CommonStatuEnum, DataStatuEnum
from common.ploto_response import PlotoResponse

logger = logging.getLogger(__name__)


class AnnotateDataHandler(APIView):
    """
    单个标注数据对象的操作类
    """

    @record_ip
    @auth_request
    def get(self, request, pk):
        """
        获取标注数据的详情
        :param request:
        :param pk:
        :return: data + list_tag
        :1、获取pk对应的数据obj
        :2、并获取对应的tag字段信息，返回list_tag，方便前端更新此字段
        """
        response = PlotoResponse()
        if not request.user.has_perm('data_mgt.view_annotatedata'):
            response.code = CommonStatuEnum.AUTHORIZATION_EXCEPTION.code
            response.msg = CommonStatuEnum.AUTHORIZATION_EXCEPTION.msg
            response.status = status.HTTP_403_FORBIDDEN
            return Response(response.dict, status=status.HTTP_403_FORBIDDEN)
        obj = AnnotateData.objects.get(id=pk)
        list_tag = obj.tag.split('|')
        try:
            serializer = AnnotateDataSerializer(obj)
            response.data = serializer.data
            response.data["list_tag"] = list_tag
            return Response(response.dict, status=status.HTTP_200_OK)
        except Exception as e:
            logger.error("序列化异常:%s, %s", repr(e), traceback.format_exc())
            response.code = CommonStatuEnum.INTERNAL_ERROR.code
            response.msg = CommonStatuEnum.INTERNAL_ERROR.msg
            response.status = status.HTTP_500_INTERNAL_SERVER_ERROR
            return Response(response.dict, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    def post(self, request):
        """
        原始标注新增接口
        :param request:
        :return: data  新增数据信息
        1、获取request写入的数据信息
        2、保存数据信息，并返回
        """
        response = PlotoResponse()
        if not request.user.has_perm('data_mgt.add_annotatedata'):
            response.code = CommonStatuEnum.AUTHORIZATION_EXCEPTION.code
            response.msg = CommonStatuEnum.AUTHORIZATION_EXCEPTION.msg
            return Response(response.dict, status=status.HTTP_403_FORBIDDEN)
        serializer = AnnotateDataSerializer(data=request.data)
        if serializer.is_valid():
            if serializer.validated_data.get("source_type", 0) == 1:
                pass
            serializer.save()
            response.data = serializer.data
            return Response(response.dict, status=status.HTTP_201_CREATED)
        logger.error("序列化校验失败:%s", serializer.errors)
        response.code = CommonStatuEnum.INTERNAL_ERROR.code
        response.msg = CommonStatuEnum.INTERNAL_ERROR.msg
        return Response(response.dict, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    def put(self, request, pk):
        """
        单条标注数据修改
        :param request
        :param pk
        :return: data  新增数据信息
        1、获取指定id的obj信息
        2、获取修改的操作  针对取回/归档/修改tag/修改其他字段
        2.1 前端传了tag字段，则表示更新了tag
           a、用. join()连接前端传的数据
           b、并更新到对应的数据字段
           c、并将更新的数据返回给前端更新
        2.2 修改其他字段：采用白名单形式控制可改字段
        """
        obj = AnnotateData.objects.get(id=pk)
        params = request.data
        # 修改 tag字段
        response = PlotoResponse()
        if not request.user.has_perm('data_mgt.change_annotatedata'):
            response.code = CommonStatuEnum.AUTHORIZATION_EXCEPTION.code
            response.msg = CommonStatuEnum.AUTHORIZATION_EXCEPTION.msg
            return Response(response.dict, status=status.HTTP_403_FORBIDDEN)
        tag = params.get('tag', '')
        if tag:
            tag_str = "|".join(tag)
            resp = AnnotateData.objects.filter(id=pk).update(tag=tag_str)
            if resp == 1:
                return Response(response.dict, status=status.HTTP_201_CREATED)
            else:
                logger.error("数据库更新失败")
                response.code = CommonStatuEnum.INTERNAL_ERROR.code
                response.msg = CommonStatuEnum.INTERNAL_ERROR.msg
                return Response(response.dict, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        # 修改其他字段
        serializer = AnnotateDataSerializer(obj, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            response.data = serializer.data
            return Response(response.dict, status=status.HTTP_201_CREATED)
        logger.error("序列化校验失败:%s", serializer.errors)
        response.code = CommonStatuEnum.INTERNAL_ERROR.code
        response.msg = CommonStatuEnum.INTERNAL_ERROR.msg
        return Response(response.dict, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    def delete(self, request, pk):
        response = PlotoResponse()
        if not request.user.has_perm('data_mgt.delete_annotatedata'):
            response.code = CommonStatuEnum.AUTHORIZATION_EXCEPTION.code
            response.msg = CommonStatuEnum.AUTHORIZATION_EXCEPTION.msg
            return Response(response.dict, status=status.HTTP_403_FORBIDDEN)
        try:
            AnnotateData.objects.filter(id=pk).update(is_delete=True)
            obj = AnnotateData.objects.get(id=pk)
            object_key = obj.object_key
            bucket_name = obj.bucket_name
            obs_resp = obs_client.deleteObject(bucket_name=bucket_name, objectKey=object_key)
            if obs_resp >= 300:
                logger.error("obs删除标注数据失败:%s", obs_resp.errorCode)
            return Response(response.dict, status=status.HTTP_204_NO_CONTENT)
        except Exception as e:
            logger.error("数据库更新失败:%s, %s", repr(e), traceback.format_exc())
            response.code = CommonStatuEnum.INTERNAL_ERROR.code
            response.msg = CommonStatuEnum.INTERNAL_ERROR.msg
            return Response(response.dict, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


class BatchAnnotateData(APIView):

    @auth_request
    def get(self, request):
        """
        批量标注数据获取
        :param request
        :return: data
        1、获取分页标记信息
        2、指定部分字段为模糊匹配搜索以及按区间搜索的字段
        3、获取根据过滤条件得到的数据信息
        4、序列化分页数据，返回给数据信息给前端
        """
        response = PlotoResponse()
        if not request.user.has_perm('data_mgt.view_annotatedata'):
            response.code = CommonStatuEnum.AUTHORIZATION_EXCEPTION.code
            response.msg = CommonStatuEnum.AUTHORIZATION_EXCEPTION.msg
            return Response(response.dict, status=status.HTTP_403_FORBIDDEN)
        params = request.query_params.dict()
        page_size = int(params.get('page_size', 10))
        page = int(params.get('page', 1))
        filter_data = fill_filter_data(params)
        annotate_data_list = AnnotateData.objects.filter(**filter_data)
        if annotate_data_list.count() == 0:
            return Response(response.dict, status=status.HTTP_200_OK)
        paginator = Paginator(annotate_data_list, page_size)
        try:
            page_original_data_list = paginator.page(page)
        except PageNotAnInteger:
            page_original_data_list = paginator.page(1)
        except EmptyPage:
            page_original_data_list = paginator.page(paginator.num_pages)
        try:
            original_data = AnnotateDataSerializer(instance=page_original_data_list, many=True)
            response.data["data_list"] = original_data.data
            response.data["count"] = paginator.count
            return Response(response.dict, status=status.HTTP_200_OK)
        except Exception as e:
            logger.error("序列化异常:%s, %s", repr(e), traceback.format_exc())
            response.code = CommonStatuEnum.INTERNAL_ERROR.code
            response.msg = CommonStatuEnum.INTERNAL_ERROR.msg
            return Response(response.dict, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    def delete(self, request, bucket_name):
        """
        批量标注数据删除
        :param request
        :param bucket_name
        :return: data
        1、获取前端传入的ids信息
        2、根据ids调用数据库的filter 和 update操作
        """
        DeleteRequestParam = namedtuple('DeleteRequestParam', ['quiet', 'objects'])
        response = PlotoResponse()
        if not request.user.has_perm('data_mgt.delete_annotatedata'):
            response.msg = CommonStatuEnum.AUTHORIZATION_EXCEPTION.msg
            response.code = CommonStatuEnum.AUTHORIZATION_EXCEPTION.code
            return Response(response.dict, status=status.HTTP_403_FORBIDDEN)
        id_list = request.POST.get('ids')
        if not id_list:
            response.msg = CommonStatuEnum.VALID_EXCEPTION.msg
            response.code = CommonStatuEnum.VALID_EXCEPTION.code
            return Response(response.dict, status=status.HTTP_400_BAD_REQUEST)
        delete_id_list = id_list.split(',')
        try:
            AnnotateData.objects.filter(id__in=delete_id_list).update(is_delete=True)
        except Exception as e:
            logger.error("数据库更新标注数据失败:%s, %s", repr(e), traceback.format_exc())
            response.code = CommonStatuEnum.INTERNAL_ERROR.code
            response.msg = CommonStatuEnum.INTERNAL_ERROR.msg
            return Response(response.dict, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        obj_list = AnnotateData.objects.get(id__in=delete_id_list)
        if obj_list:
            delete_obj_list = []
            for obj in obj_list:
                delete_obj_list.append(obj.object_key)
            delete_request_param = DeleteRequestParam(objects=delete_obj_list)
            obs_resp = obs_client.deleteObjects(bucketName=bucket_name, deleteObjectsRequest=delete_request_param)
            if obs_resp >= 300:
                logger.error("obs删除标注数据失败:%s", obs_resp.errorCode)
        return Response(response.dict, status=status.HTTP_204_NO_CONTENT)


class AnnotateDataFileHandler(APIView):

    @auth_request
    def get(self, request, pk):
        """
        标注数据修文件下载
        :param request
        :param pk
        :return: signedUrl
        1、根据id 信息获取数据库中对心的信息
        2、如果是归档存储  恢复状态未恢复中或者未恢复 是不提供下载功能的
        3、设置header的contentType信息 为直接下载不播放 调用obs的接口 在元数据中设置
        4、 调用obs接口  获取授权的url信息
        5、同步下载信息到下载信息表中
        6、同步表中的下载次数
        """
        response = PlotoResponse()
        if not request.user.has_perm('data_mgt.download_annotatedata'):
            response.code = CommonStatuEnum.AUTHORIZATION_EXCEPTION.code
            response.msg = CommonStatuEnum.AUTHORIZATION_EXCEPTION.msg
            return Response(response.dict, status=status.HTTP_403_FORBIDDEN)
        obj = AnnotateData.objects.get(id=pk)
        if None is re.match(r'^obs://\S+/.+[^/]+$', obj.url):
            logger.warning("标注数据url无效")
            response.code = DataStatuEnum.ANONYMIZE_URL_EXCEPTION.code
            response.msg = DataStatuEnum.ANONYMIZE_URL_EXCEPTION.msg
            return Response(response.dict, status=status.HTTP_400_BAD_REQUEST)
        bucket_name = obj.bucket_name
        object_key = obj.object_key
        # 当前规避：归档存储   恢复中和未恢复  不能下载
        if obj.storage_type == 2:
            restore_status = object_restore_status(bucket_name, object_key)
            if restore_status in {'no_restored', 'restoring'}:
                alarm_details = str(self.request.user) + "用户下载标注数据文件" + object_key + "失败"
                MonitorAlarm.objects.create(alarm_time=timezone.now(), alarm_content='下载异常',
                                            alarm_details=alarm_details, level=0)
                response.code = DataStatuEnum.DOWNLOAD_EXCEPTION.code
                response.msg = DataStatuEnum.DOWNLOAD_EXCEPTION.msg
                return Response(response.dict, status=status.HTTP_400_BAD_REQUEST)
        headers = SetObjectMetadataHeader()
        headers.contentType = "application/octet-stream"
        res = obs_client.setObjectMetadata(bucket_name, object_key, headers=headers)
        if res.status >= 300:
            logger.error("设置标注对象元数据失败:%s, %s", res.errorCode, res.errorMessage)
            response.code = DataStatuEnum.SET_METADATA_EXCEPTION.code
            response.msg = DataStatuEnum.SET_METADATA_EXCEPTION.msg
            return Response(response.dict, status=status.HTTP_400_BAD_REQUEST)
        try:
            resp = obs_client.createSignedUrl('GET', bucket_name, object_key, expires=3600)
        except Exception as e:
            alarm_details = str(self.request.user) + "用户下载标注数据文件" + object_key + "失败"
            try:
                MonitorAlarm.objects.create(alarm_time=timezone.now(), alarm_content='下载异常',
                                            alarm_details=alarm_details, level=0)
            except Exception as e:
                logger.error("告警数据表插入数据失败: %s, %s", repr(e), traceback.format_exc())
            logger.error("创建标注数据下载url:%s, %s", repr(e), traceback.format_exc())
            response.code = DataStatuEnum.GET_DOWNLOAD_URL_EXCEPTION.code
            response.msg = DataStatuEnum.GET_DOWNLOAD_URL_EXCEPTION.msg
            return Response(response.dict, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        # 同步下载数据到数据下载信息表中
        try:
            DataDownloadInfo.objects.create(data_id=pk, data_type=1, data_version=obj.version,
                                            user=self.request.user)
            info_content = str(self.request.user) + "用户下载标注数据文件" + object_key
            MonitorRealTimeInfo.objects.create(create_time=timezone.now(), info_content=info_content)
            AnnotateData.objects.filter(id=pk).update(download_times=F('download_times') + 1)
            response.data["data"] = resp.signedUrl
            return Response(response.dict, status=status.HTTP_200_OK)
        except Exception as e:
            logger.error("数据下载信息表同步失败:%s, %s", repr(e), traceback.format_exc())
            response.code = CommonStatuEnum.INTERNAL_ERROR.code
            response.msg = CommonStatuEnum.INTERNAL_ERROR.msg
            return Response(response.dict, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


class AnnotateDataRestore(APIView):
    AnnotateParam = \
        namedtuple('AnnotateParam', ['days_num', 'fail_dict', 'obj_list', 'success_object', 'tier'])

    def post(self, request):
        response = PlotoResponse()
        if not request.user.has_perm('data_mgt.restore_annotatedata'):
            response.code = CommonStatuEnum.AUTHORIZATION_EXCEPTION.code
            response.msg = CommonStatuEnum.AUTHORIZATION_EXCEPTION.msg
            return Response(response.dict, status=status.HTTP_403_FORBIDDEN)
        params = request.data
        success_object = []
        days_num = params.get("days_num", 30)
        rate = params.get("rate", 'STANDARD')
        id_list = params.get('ids', [])
        restore_list = id_list.split(',')
        fail_dict = {}
        tier = RestoreTier.EXPEDITED if rate == 'EXPEDITED' else RestoreTier.STANDARD
        obj_list = AnnotateData.objects.filter(id__in=restore_list)
        if obj_list is None:
            logger.error("查询标注数据列表为空")
            response.code = DataStatuEnum.NOT_EXIST_EXCEPTION.code
            response.msg = DataStatuEnum.NOT_EXIST_EXCEPTION.msg
            return Response(response.dict, status=status.HTTP_404_NOT_FOUND)
        annotate_param = self.AnnotateParam(days_num=days_num, fail_dict=fail_dict,
                                            obj_list=obj_list, success_object=success_object, tier=tier)
        self._do_restore(annotate_param)
        if success_object:
            if fail_dict:
                response.code = DataStatuEnum.RESTORE_EXCEPTIOM.code
                response.msg = DataStatuEnum.RESTORE_EXCEPTIOM.msg
                response.data = fail_dict
                return Response(response.dict, status=status.HTTP_206_PARTIAL_CONTENT)
            return Response(response.dict, status=status.HTTP_200_OK)
        else:
            logger.error("恢复数据失败")
            response.code = DataStatuEnum.RESTORE_EXCEPTIOM.code
            response.msg = DataStatuEnum.RESTORE_EXCEPTIOM.msg
            response.data = fail_dict
            return Response(response.dict, status=status.HTTP_400_BAD_REQUEST)

    def _do_restore(self, annotate_param):
        days_num, fail_dict, obj_list, success_object, tier = annotate_param
        for obj in obj_list:
            if None is re.match(r'^obs://\S+/.+[^/]+$', obj.url):
                fail_dict[obj.name] = DataStatuEnum.ANONYMIZE_URL_EXCEPTION.msg
                logger.warning("标注数据%s url无效", obj.name)
                continue
            bucket_name = obj.bucket_name
            object_key = obj.object_key
            restore_status = object_restore_status(bucket_name, object_key)
            if restore_status in {'no_restored', 'restored'}:
                resp = obs_client.restoreObject(bucket_name, object_key, days_num, tier)
                if resp.status < 300:
                    try:
                        ObsAsyncJobStatus.objects.create(job_id=obj.id, job_data_type=0, job_type=get_job_type(tier),
                                                         job_bucket=bucket_name, job_object=object_key,
                                                         next_run_time=timezone.now())
                    except Exception as e:
                        logger.error("标注异步任务表新增数据失败:%s, %s", repr(e), traceback.format_exc())
                        fail_dict[obj.name] = CommonStatuEnum.INTERNAL_ERROR.msg
                    try:
                        # 对于取回的成功的对象 需要修改其恢复状态  当前设置为恢复中 规避方法下一次操作的时候先查（恢复中->已恢复）
                        AnnotateData.objects.filter(id=obj.id).update(restoration_status=2)
                    except Exception as e:
                        logger.error("标注数据表更新失败:%s, %s", repr(e), traceback.format_exc())
                        fail_dict[obj.name] = CommonStatuEnum.INTERNAL_ERROR.msg
                    success_object.append(obj.name)
                else:
                    fail_dict[obj.name] = resp.errorMessage
            else:
                fail_dict[obj.name] = DataStatuEnum.RESTORING_NOT_ALLOWED.msg


class AnnotateDataModifyStorageClass(APIView):
    AnnotateParam = \
        namedtuple('AnnotateParam', ['fail_dict', 'headers', 'obj', 'resp', 'storage_class', 'success_object'])
    ModifyParam = namedtuple('ModifyParam', ['fail_dict', 'headers', 'obj_list', 'success_object', 'storage_class'])

    def post(self, request):
        response = PlotoResponse()
        if not request.user.has_perm('data_mgt.modify_class_annotatedata'):
            response.code = CommonStatuEnum.AUTHORIZATION_EXCEPTION.code
            response.msg = CommonStatuEnum.AUTHORIZATION_EXCEPTION.msg
            return Response(response.dict, status=status.HTTP_403_FORBIDDEN)
        params = request.data
        success_object = []
        id_list = params.get('ids', [])
        storage_class = params.get("storage_class")
        headers = SetObjectMetadataHeader()
        set_header(headers, storage_class)
        fail_dict = {}
        obj_list = AnnotateData.objects.filter(id__in=id_list)
        if obj_list is None:
            logger.error("未找到目标标注数据")
            response.code = DataStatuEnum.NOT_EXIST_EXCEPTION.code
            response.msg = DataStatuEnum.NOT_EXIST_EXCEPTION.msg
            return Response(response.dict, status=status.HTTP_404_NOT_FOUND)
        modify_param = self.ModifyParam(fail_dict=fail_dict, headers=headers, obj_list=obj_list,
                                        success_object=success_object, storage_class=storage_class)
        self._get_modify_result(modify_param)
        # 结果返回
        if success_object:
            if fail_dict:
                response.msg = DataStatuEnum.MODIFY_CLASS_EXCEPTION.msg
                response.code = DataStatuEnum.MODIFY_CLASS_EXCEPTION.code
                response.data = fail_dict
                return Response(response.dict, status=status.HTTP_206_PARTIAL_CONTENT)
            else:
                return Response(response.dict, status=status.HTTP_200_OK)
        response.msg = DataStatuEnum.MODIFY_CLASS_EXCEPTION.msg
        response.code = DataStatuEnum.MODIFY_CLASS_EXCEPTION.code
        response.data = fail_dict
        return Response(response.dict, status=status.HTTP_400_BAD_REQUEST)

    def _get_modify_result(self, modify_param):
        fail_dict, headers, obj_list, success_object, storage_class = modify_param
        for obj in obj_list:
            source_storage_class = obj.storage_type
            url = obj.url
            if None is re.match(r'^obs://\S+/.+[^/]+$', url):
                fail_dict[obj.name] = DataStatuEnum.ANONYMIZE_URL_EXCEPTION.msg
                logger.warning("标注数据%s url无效", obj.name)
                continue
            bucket_name = obj.bucket_name
            object_key = obj.object_key
            if source_storage_class == 2:
                restore_status = object_restore_status(bucket_name, object_key)
                if restore_status == 'restored':
                    resp = obs_client.setObjectMetadata(bucket_name, object_key, headers=headers)
                    annotate_param = self.AnnotateParam(fail_dict=fail_dict, headers=headers, obj=obj, resp=resp,
                                                        storage_class=storage_class, success_object=success_object)
                    self._do_response(annotate_param)
                else:
                    fail_dict[obj.name] = DataStatuEnum.MODIFY_CLASS_NOT_ALLOWED.msg
            else:
                resp = obs_client.setObjectMetadata(bucket_name, object_key, headers=headers)
                annotate_param = self.AnnotateParam(fail_dict=fail_dict, headers=headers, obj=obj, resp=resp,
                                                    storage_class=storage_class, success_object=success_object)
                self._do_response(annotate_param)

    def _do_response(self, annotate_param):
        fail_dict, headers, obj, resp, storage_class, success_object = annotate_param
        if resp.status < 300:
            try:
                # 执行成功的id  更新对应的字段  修改为归档存储时 恢复状态为未恢复  修改为其他存储类别 则恢复状态为--
                if headers.storageClass == 'COLD':
                    AnnotateData.objects.filter(id=obj.id).update(storage_type=storage_class,
                                                                  restoration_status=1)
                else:
                    AnnotateData.objects.filter(id=obj.id).update(storage_type=storage_class,
                                                                  restoration_status=0)
            except Exception as e:
                logger.error("标注数据表更新失败:%s, %s", repr(e), traceback.format_exc())
                fail_dict[obj.name] = CommonStatuEnum.INTERNAL_ERROR.msg
            success_object.append(obj.name)
        else:
            logger.error("obs调用失败%s, %s", resp.errorCode, resp.errorMessage)
            fail_dict[obj.name] = resp.errorMessage


class AnnotateDataCirculate(APIView):

    def post(self, request):
        pass
